/**
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

package org.gudy.azureus2.core3.torrent.impl;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.internat.LocaleTorrentUtil;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentFile;
import org.gudy.azureus2.core3.util.AENetworkClassifier;
import org.gudy.azureus2.core3.util.AETemporaryFileHandler;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.core3.util.TorrentUtils;

import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.tag.Tag;
import com.aelitis.azureus.core.util.CopyOnWriteList;

/**
 * Class to store one Torrent file's info. Used to populate table and store user's choices.
 * <P>
 * This was copied out of the UI code, and still contains some crap code
 */
public class TorrentOpenOptions {
    private final static String PARAM_DEFSAVEPATH = "Default save path";

    private final static String PARAM_MOVEWHENDONE = "Move Completed When Done";

    public final static int QUEUELOCATION_BOTTOM = 1;

    public final static int QUEUELOCATION_TOP = 0;

    public final static int STARTMODE_FORCESTARTED = 2;

    public final static int STARTMODE_QUEUED = 0;

    public final static int STARTMODE_SEEDING = 3;

    public final static int STARTMODE_STOPPED = 1;

    /** Where the torrent came from. Could be a file, URL, or some other text */
    /** @todo: getter/setters */
    public String sOriginatingLocation;

    /** Filename the .torrent is saved to */
    /** @todo: getter/setters */
    public String sFileName;

    private String sDestDir;

    private String manualRename; // if user has manually renamed the top level folder

    /** for multifiletorrents and change location */
    /** @todo: getter/setters */
    private String sDestSubDir;

    private boolean explicitDataDir;

    /** @todo: getter/setters */
    private TOTorrent torrent;

    private long totalSize;

    /** @todo: getter/setters */
    public int iStartID;

    /** @todo: getter/setters */
    public int iQueueLocation;

    /** @todo: getter/setters */
    public boolean isValid;

    /** @todo: getter/setters */
    public boolean bDeleteFileOnCancel;

    private TorrentOpenFileOptions[] files = null;

    /** @todo: getter/setters */
    public boolean disableIPFilter = false;

    private Map<Integer, File> initial_linkage_map = null;

    private CopyOnWriteList<FileListener> fileListeners = new CopyOnWriteList<FileListener>(1);

    public Map<String, Boolean> peerSource = new HashMap<String, Boolean>();
    public Map<String, Boolean> enabledNetworks = new HashMap<String, Boolean>();

    private List<Tag> initialTags = new ArrayList<Tag>();

    private List<List<String>> updatedTrackers;

    private int max_up;
    private int max_down;

    // add stuff here -> update the clone constructor

    /**
     * Init
     * 
     * @param sFileName
     * @param torrent
     * @param bDeleteFileOnCancel
     */
    public TorrentOpenOptions(String sFileName, TOTorrent torrent, boolean bDeleteFileOnCancel) {
        this();
        this.bDeleteFileOnCancel = bDeleteFileOnCancel;
        this.sFileName = sFileName;
        this.sOriginatingLocation = sFileName;
        this.setTorrent(torrent);
    }

    public TorrentOpenOptions() {
        iStartID = getDefaultStartMode();
        iQueueLocation = QUEUELOCATION_BOTTOM;
        isValid = true;
        this.sDestDir = COConfigurationManager.getStringParameter(PARAM_DEFSAVEPATH);

        for (int i = 0; i < AENetworkClassifier.AT_NETWORKS.length; i++) {

            String nn = AENetworkClassifier.AT_NETWORKS[i];

            String config_name = "Network Selection Default." + nn;

            enabledNetworks.put(nn, COConfigurationManager.getBooleanParameter(config_name));
        }
    }

    /**
     * clones everything except files and torrent
     * 
     * @param toBeCloned
     */
    public TorrentOpenOptions(TorrentOpenOptions toBeCloned) {
        this.sOriginatingLocation = toBeCloned.sOriginatingLocation;
        this.sFileName = toBeCloned.sFileName;
        this.sDestDir = toBeCloned.sDestDir;
        this.sDestSubDir = toBeCloned.sDestSubDir;
        this.iStartID = toBeCloned.iStartID;
        this.iQueueLocation = toBeCloned.iQueueLocation;
        this.isValid = toBeCloned.isValid;
        this.bDeleteFileOnCancel = toBeCloned.bDeleteFileOnCancel;
        this.disableIPFilter = toBeCloned.disableIPFilter;
        // this.torrent = ... // no clone
        // this.initial_linkage_map = ... // no clone
        // this.files = ... // no clone
        this.peerSource = toBeCloned.peerSource == null ? null : new HashMap<String, Boolean>(toBeCloned.peerSource);
        this.enabledNetworks = toBeCloned.enabledNetworks == null ? null : new HashMap<String, Boolean>(toBeCloned.enabledNetworks);
        this.initialTags = toBeCloned.initialTags == null ? null : new ArrayList<Tag>(toBeCloned.initialTags);

        if (toBeCloned.updatedTrackers != null) {
            updatedTrackers = new ArrayList<List<String>>();
            for (List<String> l : toBeCloned.updatedTrackers) {
                updatedTrackers.add(new ArrayList<String>(l));
            }
        }
        this.max_up = toBeCloned.max_up;
        this.max_down = toBeCloned.max_down;
    }

    public static int getDefaultStartMode() {
        return (COConfigurationManager.getBooleanParameter("Default Start Torrents Stopped")) ? STARTMODE_STOPPED : STARTMODE_QUEUED;
    }

    public File getInitialLinkage(int index) {
        return initial_linkage_map == null ? null : (initial_linkage_map.get(index));
    }

    public String getParentDir() {
        return sDestDir;
    }

    public void setParentDir(String parentDir) {
        sDestDir = parentDir;
        parentDirChanged();
    }

    public void setManualRename(String manualRename) {
        this.manualRename = manualRename;
    }

    public String getManualRename() {
        return (manualRename);
    }

    public String getSubDir() {
        return (sDestSubDir);
    }

    public void setExplicitDataDir(String parent_dir, String sub_dir) {
        sDestDir = parent_dir;
        sDestSubDir = sub_dir;

        explicitDataDir = true;

        parentDirChanged();
    }

    public boolean isExplicitDataDir() {
        return (explicitDataDir);
    }

    public boolean isSimpleTorrent() {
        return (torrent.isSimpleTorrent());
    }

    public String getDataDir() {
        if (torrent.isSimpleTorrent())
            return sDestDir;
        return new File(sDestDir, sDestSubDir == null ? FileUtil.convertOSSpecificChars(getTorrentName(), true) : sDestSubDir).getPath();
    }

    private String getSmartDestDir() {
        String sSmartDir = sDestDir;
        try {
            String name = getTorrentName();
            String torrentFileName = sFileName == null ? "" : new File(sFileName).getName().replaceFirst("\\.torrent$", "");
            int totalSegmentsLengths = 0;

            String[][] segments = { name.split("[^a-zA-Z]+"), torrentFileName.split("[^a-zA-Z]+") };
            List downloadManagers = AzureusCoreFactory.getSingleton().getGlobalManager().getDownloadManagers();

            for (int x = 0; x < segments.length; x++) {
                String[] segmentArray = segments[x];
                for (int i = 0; i < segmentArray.length; i++) {
                    int l = segmentArray[i].length();
                    if (l <= 1) {
                        continue;
                    }
                    segmentArray[i] = segmentArray[i].toLowerCase();
                    totalSegmentsLengths += l;
                }
            }

            String temp_dir = AETemporaryFileHandler.getTempDirectory().getAbsolutePath().toLowerCase(Locale.US);

            int maxMatches = 0;
            DownloadManager match = null;
            for (Iterator iter = downloadManagers.iterator(); iter.hasNext();) {
                DownloadManager dm = (DownloadManager) iter.next();

                if (dm.getState() == DownloadManager.STATE_ERROR) {
                    continue;
                }

                DownloadManagerState dms = dm.getDownloadState();

                if (dms.getFlag(DownloadManagerState.FLAG_LOW_NOISE) || dms.getFlag(DownloadManagerState.FLAG_METADATA_DOWNLOAD)) {

                    continue;
                }

                // had users with files ending up in the temp dir (5100/5200) so as an attempt to stop this
                // lets excluded anything dodgy (might also have been to do with metadata downloads we now
                // filter above)

                if (dm.getSaveLocation().getAbsolutePath().toLowerCase(Locale.US).startsWith(temp_dir)) {

                    continue;
                }

                int numMatches = 0;

                String dmName = dm.getDisplayName().toLowerCase();

                for (int x = 0; x < segments.length; x++) {
                    String[] segmentArray = segments[x];
                    for (int i = 0; i < segmentArray.length; i++) {
                        int l = segmentArray[i].length();
                        if (l <= 1) {
                            continue;
                        }

                        String segment = segmentArray[i];

                        if (dmName.indexOf(segment) >= 0) {
                            numMatches += l;
                        }
                    }
                }

                if (numMatches > maxMatches) {
                    maxMatches = numMatches;
                    match = dm;
                }
            }
            if (match != null) {
                // System.out.println(match + ": " + (maxMatches * 100 / totalSegmentsLengths) + "%\n");
                int iMatchLevel = (maxMatches * 100 / totalSegmentsLengths);
                if (iMatchLevel >= 30) {
                    File f = match.getSaveLocation();
                    if (!f.isDirectory() || match.getDiskManagerFileInfo().length > 1) {
                        // don't place data within another torrent's data dir
                        f = f.getParentFile();
                    }

                    if (f != null && f.isDirectory()) {
                        sSmartDir = f.getAbsolutePath();
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return sSmartDir;
    }

    public List<Tag> getInitialTags() {
        return (new ArrayList<Tag>(initialTags));
    }

    public void setInitialTags(List<Tag> tags) {
        initialTags = tags;
    }

    public List<List<String>> getTrackers(boolean if_updated) {
        if (updatedTrackers != null) {

            return (updatedTrackers);
        }

        if (if_updated) {

            return (null);
        }

        if (torrent == null) {

            return (new ArrayList<List<String>>(0));

        } else {

            return (TorrentUtils.announceGroupsToList(torrent));
        }
    }

    public void setTrackers(List<List<String>> trackers) {
        updatedTrackers = trackers;
    }

    public void setMaxUploadSpeed(int kbs) {
        max_up = kbs;
    }

    public int getMaxUploadSpeed() {
        return (max_up);
    }

    public void setMaxDownloadSpeed(int kbs) {
        max_down = kbs;
    }

    public int getMaxDownloadSpeed() {
        return (max_down);
    }

    public TorrentOpenFileOptions[] getFiles() {
        if (files == null && torrent != null) {
            TOTorrentFile[] tfiles = torrent.getFiles();
            files = new TorrentOpenFileOptions[tfiles.length];
            for (int i = 0; i < files.length; i++) {
                files[i] = new TorrentOpenFileOptions(this, tfiles[i], i);
            }
        }

        return files;
    }

    public long getTotalSize() {
        if (totalSize == 0) {

            TorrentOpenFileOptions[] files = getFiles();

            if (files != null) {

                for (TorrentOpenFileOptions file : files) {

                    totalSize += file.lSize;
                }
            }
        }

        return (totalSize);
    }

    public String getTorrentName() {
        return TorrentUtils.getLocalisedName(torrent);
    }

    public boolean allFilesMoving() {
        TorrentOpenFileOptions[] files = getFiles();
        for (int j = 0; j < files.length; j++) {
            if (files[j].isLinked()) {
                return false;
            }
        }
        return true;
    }

    public boolean allFilesExist() {
        // check if all selected files exist
        TorrentOpenFileOptions[] files = getFiles();
        for (int i = 0; i < files.length; i++) {
            TorrentOpenFileOptions fileInfo = files[i];
            if (!fileInfo.isToDownload())
                continue;

            File file = fileInfo.getDestFileFullName();
            if (!file.exists() || file.length() != fileInfo.lSize) {
                return false;
            }
        }
        return true;
    }

    public void renameDuplicates() {
        if (iStartID == STARTMODE_SEEDING || !COConfigurationManager.getBooleanParameter("DefaultDir.AutoSave.AutoRename") || allFilesExist()) {
            return;
        }

        if (!torrent.isSimpleTorrent()) {
            if (new File(getDataDir()).isDirectory()) {
                File f;
                int idx = 0;
                do {
                    idx++;
                    f = new File(getDataDir() + "-" + idx);
                }
                while (f.isDirectory());

                sDestSubDir = f.getName();
            }
        } else {
            // should only be one file
            TorrentOpenFileOptions[] fileInfos = getFiles();
            for (int i = 0; i < fileInfos.length; i++) {
                TorrentOpenFileOptions info = fileInfos[i];

                File file = info.getDestFileFullName();
                int idx = 0;
                while (file.exists()) {
                    idx++;
                    file = new File(info.getDestPathName(), idx + "-" + info.getDestFileName());
                }

                info.setDestFileName(file.getName(), false);
            }
        }
    }

    /*
     * private Boolean has_multiple_small_files = null; private boolean hasMultipleSmallFiles() { TorrentFileInfo[] tfi_files = getFiles(); if
     * (tfi_files.length <= MAX_NODOWNLOAD_COUNT) return false; int small_files_counted = 0; for (int i=0; i<tfi_files.length; i++) { if
     * (tfi_files[i].lSize < MIN_NODOWNLOAD_SIZE) { small_files_counted++; if (small_files_counted > MAX_NODOWNLOAD_COUNT) { return true; } } } return
     * false; }
     */

    // Indicates whether all files in this torrent can be deselected
    // (if not, then it occurs on a per-file basis).
    public boolean okToDisableAll() {
        return true;

        /*
         * if (iStartID == STARTMODE_SEEDING) return true; // Do we have multiple small files? We'll allow all of them to // be disabled if we do. if
         * (has_multiple_small_files == null) { has_multiple_small_files = new Boolean(hasMultipleSmallFiles()); } // You can disable all files if
         * there are lots of small files. return has_multiple_small_files.booleanValue();
         */
    }

    public TOTorrent getTorrent() {
        return torrent;
    }

    public void setTorrent(TOTorrent torrent) {
        this.torrent = torrent;

        // only apply the 'smart' save path directory guessing logic if the user hasn't set an explicit
        // save path (5100/5200 was missing this logic that previously existed before the open-torrent-options
        // was rewritten and this has caused quite some grief with downloads ending up in unexpected locations...)

        if (COConfigurationManager.getBooleanParameter("DefaultDir.BestGuess") && !COConfigurationManager.getBooleanParameter(PARAM_MOVEWHENDONE)) {

            String def_save_path = COConfigurationManager.getStringParameter(PARAM_DEFSAVEPATH);

            if (def_save_path == null || def_save_path.trim().length() == 0) {

                this.sDestDir = getSmartDestDir();
            }
        }

        if (torrent == null) {
            initial_linkage_map = null;
        } else {
            initial_linkage_map = TorrentUtils.getInitialLinkage(torrent);

            // Force a check on the encoding, will prompt user if we dunno
            try {
                LocaleTorrentUtil.getTorrentEncoding(torrent);
            } catch (Exception e) {
                e.printStackTrace();
            }

            Set<String> tracker_hosts = TorrentUtils.getUniqueTrackerHosts(torrent);

            Set<String> networks = new HashSet<String>();

            boolean decentralised = false;

            for (String host : tracker_hosts) {

                if (TorrentUtils.isDecentralised(host)) {

                    decentralised = true;

                } else {

                    String network = AENetworkClassifier.categoriseAddress(host);

                    networks.add(network);
                }
            }

            List<String> network_cache = TorrentUtils.getNetworkCache(torrent);

            networks.addAll(network_cache);

            // could do something here if multiple networks to get user to decide what to do...

            boolean enable_i2p = networks.contains(AENetworkClassifier.AT_I2P);

            if (!enable_i2p) {

                // if torrent is purely decentralised then we don't really know what network so enable it

                if (tracker_hosts.size() == 1 && decentralised) {

                    enable_i2p = true;
                }
            }

            if (enable_i2p) {

                String[] providers = { "azneti2p", "azneti2phelper" };

                boolean found = false;

                for (String provider : providers) {

                    if (AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByID(provider) != null) {

                        found = true;

                        break;
                    }
                }

                if (found) {

                    enabledNetworks.put(AENetworkClassifier.AT_I2P, true);

                    // disable public if purely i2p

                    if (networks.contains(AENetworkClassifier.AT_I2P) && networks.size() == 1) {

                        enabledNetworks.put(AENetworkClassifier.AT_PUBLIC, false);
                    }
                }
            }

            boolean enable_tor = networks.contains(AENetworkClassifier.AT_TOR);

            if (enable_tor) {

                String[] providers = { "aznettor" };

                boolean found = false;

                for (String provider : providers) {

                    if (AzureusCoreFactory.getSingleton().getPluginManager().getPluginInterfaceByID(provider) != null) {

                        found = true;

                        break;
                    }
                }

                if (found) {

                    enabledNetworks.put(AENetworkClassifier.AT_TOR, true);

                    // disable public if not selected

                    if (!networks.contains(AENetworkClassifier.AT_PUBLIC)) {

                        enabledNetworks.put(AENetworkClassifier.AT_PUBLIC, false);
                    }
                }
            }

            renameDuplicates();
        }
    }

    public void addListener(FileListener l) {
        fileListeners.add(l);
    }

    public void removeListener(FileListener l) {
        fileListeners.remove(l);
    }

    public interface FileListener {
        public void toDownloadChanged(TorrentOpenFileOptions torrentOpenFileOptions, boolean toDownload);

        public void priorityChanged(TorrentOpenFileOptions torrentOpenFileOptions, int priority);

        public void parentDirChanged();
    }

    public void fileDownloadStateChanged(TorrentOpenFileOptions torrentOpenFileOptions, boolean toDownload) {
        for (FileListener l : fileListeners) {
            try {
                l.toDownloadChanged(torrentOpenFileOptions, toDownload);
            } catch (Throwable e) {
                Debug.out(e);
            }
        }
    }

    public void filePriorityStateChanged(TorrentOpenFileOptions torrentOpenFileOptions, int priority) {
        for (FileListener l : fileListeners) {
            try {
                l.priorityChanged(torrentOpenFileOptions, priority);
            } catch (Throwable e) {
                Debug.out(e);
            }
        }
    }

    public void parentDirChanged() {
        for (FileListener l : fileListeners) {
            try {
                l.parentDirChanged();
            } catch (Throwable e) {
                Debug.out(e);
            }
        }
    }
}